# jukebox.py - Jukee
#
# Copyright (C) 2008 Ruben Medellin <ruben.medellin.c@gmail.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the
#    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
#    Boston, MA  02111-1307  USA

"""
Jukebox server. This module contains the classes that control the events
on the jukebox properly as a server
"""

from threading import Thread
import Pyro.naming
import Pyro.EventService.Server
from Pyro.EventService.Clients import Publisher
from Pyro.errors import *
from jukee.server.player import Player
from jukee.server.playlist import Playlist
from jukee.libs.Observable import Observable, Observer
from jukee.common.clientmodel import ClientModel
from jukee.utils.utils import debug, warn, error
import md5, time
import gtk

#################### Player thread ####################

class PlayerThread(Thread):
  """This class will spawn the playing of the pipeline in its own separated thread"""

  def __init__(self, player):
    """Sets up the PlayerThread object as a daemon thread"""

    Thread.__init__(self)
    self.setDaemon(True)
    self.setName("jukee_player")
    self.player = player

  def run(self):
    """
    Runs the Player in a background thread. It first initializes the gtk threading
    with a call to gtk.gdk.threads_init. FIXME: It doensn't anymore. This function is
    called inside the Player instance, each time it starts playing.
    """
    #\gtk.gdk.threads_init()
    #gtk.gdk.threads_enter()
    try:
      self.player.play()
    except:
      #gtk.gdk.threads_leave()
      self.player.stop()

  def stop(self):
    """Stops the player instance and marks daemon as not alive"""
    #gtk.gdk.threads_leave()
    self.player.stop()
    self.isAlive = False
    
#################### Jukebox ####################

class Jukebox(Pyro.core.ObjBase, Publisher, Observable, Observer):
  """
  An instance of this class will be used from Pyro as the remote objects, clients will 
  interact with it and send messages to it. This object will be responsible for publishing
  (via the EventServer) status changes to the connected clients.
  """

  CLIENT_JOINED       = "client_joined"
  NEW_SONG_PLAYING    = "new_song_playing"
  
  SALT                = "juk33!5alT"

  def __init__(self, name, description, gst_port = 8080):
    """
    Starts the jukebox, and gives it a name and a description.
    The name identifies the jukebox in the Name Server, and the description is
    information for the clients.
    """

    # Sets up for Pyro remoting and observable pattern, for interfaces
    Pyro.core.ObjBase.__init__(self)
    Publisher.__init__(self)
    Observable.__init__(self)
    Observer.__init__(self)

    # Initializes attributes
    self.name = name
    self.description = description
    self.gst_port = gst_port
        
    #Sets up the player
    self.player = Player(
      host = Pyro.config.PYRO_NS_HOSTNAME,
      port = self.gst_port,
      #onPlaySong = (lambda song: self.newSongPlaying(song))
    ) ##FIXME: Arbitrary port number by now

    PlayerThread(self.player).start()
    debug("Player thread is running")
    self.observe(self.player)
    
    # Creates the playlist.
    # TODO: The playlist should be the remote object
    self.playlist = Playlist()
    self.clients = {}

  def addSong(self, song):
    """Adds a song to the playlist and notifies the clients"""
    self.playlist.add(song)
    self.publish("SONG_ADDED", self.playlist)
      ##TODO: Publish the whole playlist? Or just notify the changes

  def publish(self, *args):
    event_name, rest = args[0], args[1:]
    new_event_name = "JUKEE." + self.name + event_name
    Publisher.publish(self, new_event_name, *rest)
    debug("Jukebox publishing " + str(new_event_name) + " : " + str(rest))

  def processEvent(self, notifier, event_name, arguments):
    if notifier == self.player:
      if event_name == Player.NEW_SONG_PLAYING:
        song = arguments[0]
        debug("Song " + str(song) + " is playing")
        self.broadcastEvent(Jukebox.NEW_SONG_PLAYING, song)
        self.publish("SONG_PLAYING", song)

  def connect(self, client_model):
    client_id = md5.new(client_model['name'] + Jukebox.SALT + client_model['hostname']).hexdigest()
    debug("Client " + client_model['name'] + '@' +
            client_model['hostname'] + " is trying to connect")
    if self.clients.get(client_id, None):
            # Do not publish the event. Either is a reconnection or an invalid try
      warn("Client " + client_id + "(" + client_model['name'] + ")  already connected")
    else:
      self.clients[client_id] = client_model
      self.publish("NEW_CLIENT", "Client " + client_model['name'] + " joined the jukebox")
      # TODO Send the whole client model to observers
      self.broadcastEvent(Jukebox.CLIENT_JOINED, client_model)

    return client_id

  def get_config_params(self):
    return {'name':self.name, 'description':self.description, 'gst_port':self.gst_port}

#################### Remote server ####################

class JukeeServer(Thread):
  """
  This class is responsible for creating the Pyro server which will handle
  the remote objects
  """
  
  def __init__(self, name, description):
    Thread.__init__(self)
    self.ready = False
    self.name = name
    self.description = description
    self.setDaemon(True)

  def run(self):
    debug("Creating Pyro server (" + self.name + ")")
    Pyro.core.initServer()

    # Create the daemon
    self.daemon = Pyro.core.Daemon(host=Pyro.config.PYRO_NS_HOSTNAME)  ##IMPROVE
    self.daemon.useNameServer(Pyro.naming.NameServerLocator().getNS())

    # Create the jukebox object and connect to the server
    self.jukebox = Jukebox(self.name, self.description)
    self.daemon.connect(self.jukebox, self.name)
    self.ready = True

    self.daemon.requestLoop()
    debug("Jukee server activated. Waiting for requests...")
  
  def stop(self):
    self.daemon.shutdown(True)
    debug("Pyro server shutted down")

